#include "Renderer.h"
#include "GlowDevice.h"
#include "GL/glew.h"
#include "GL/glfw.h"

#define BUFFER_OFFSET(a) ((char*)NULL + (a))

namespace glow
{
	Renderer *Renderer::_inst = NULL;
	
	Renderer::Renderer()
	{
	}

	Renderer::~Renderer()
	{
	}

	void Renderer::init(float near, float far, int fov, Color3i bgColor, int swapInterval)
	{
		_size = Vector2i(Device::instance()->getSize().X, Device::instance()->getSize().Y);

		_near = near;
		_far = far;
		_fov = fov;
		_bgColor = bgColor;
		_indexBuffer = NULL;
		_activeShader = NULL;
		_start_time = 0;

		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		gluPerspective(_fov, (double)_size.X/_size.Y, _near, _far);
		glEnable(GL_DEPTH_TEST);
		glColor3ub(_bgColor.R, _bgColor.G, _bgColor.B);

		glEnable(GL_CULL_FACE);
		glPolygonMode(GL_FRONT, GL_FILL);

		glfwSwapInterval(swapInterval);
	}

	void Renderer::resize(int width, int height)
	{
		_size = Vector2i(width, height);
		glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		glViewport(0, 0, _size.X, _size.Y);
		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		gluPerspective(_fov, (double)_size.X/_size.Y, _near, _far);
	}

	bool Renderer::initExtensions()
	{
		GLenum code = glewInit();
		if(code != GLEW_OK)
		{
			Logger::log() << "Couldn't initialize extensions : " << glewGetErrorString(code) << "\n";
			return false;
		}
		else return true;
	}

	bool Renderer::checkExtension(const char *name)
	{
		if(glewIsSupported(name)) return true;
		else return false;
	}

	float Renderer::getNear() const
	{
		return _near;
	}

	float Renderer::getFar() const
	{
		return _far;
	}

	int Renderer::getFov() const
	{
		return _fov;
	}

	Vector2i Renderer::getSize() const
	{
		return _size;
	}

	Color3i Renderer::getBgColor() const
	{
		return _bgColor;
	}

	void Renderer::set2DMode()
	{
		glDisable(GL_DEPTH_TEST);
		glMatrixMode(GL_PROJECTION);
		glPushMatrix();
		glLoadIdentity();
		gluOrtho2D(0, _size.X, 0, _size.Y);
		glMatrixMode(GL_MODELVIEW);
		glPushMatrix();
		glLoadIdentity();
	}

	void Renderer::end2DMode()
	{
		glMatrixMode(GL_PROJECTION);
		glPopMatrix();
		glMatrixMode(GL_MODELVIEW);
		glPopMatrix();
		glEnable(GL_DEPTH_TEST);
	}

	void Renderer::setPolygonMode(GLenum face, GLenum mode)
	{
		glPolygonMode(face, mode);
	}

	void Renderer::lookAt(Vector3d cam, Vector3d target, Vector3d axes)
	{
		gluLookAt(cam.X, cam.Y, cam.Z,
				  target.X, target.Y, target.Z,
				  axes.X, axes.Y, axes.Z);
	}

	void Renderer::beginScene(Color4f color)
	{
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();
		glClearColor(color.R, color.G, color.B, color.A);
	}

	void Renderer::endScene()
	{
		glfwSwapBuffers();
	}

	void Renderer::setMatrix(GLenum mode)
	{
		glMatrixMode(mode);
	}

	void Renderer::pushMatrix()
	{
		glPushMatrix();
	}

	void Renderer::popMatrix()
	{
		glPopMatrix();
	}

	void Renderer::scale(const Vector3d &scale)
	{
		glScaled(scale.X, scale.Y, scale.Z);
	}

	void Renderer::translate(const Vector3d &pos)
	{
		glTranslated(pos.X , pos.Y , pos.Z);
	}

	void Renderer::rotate(GLdouble angle, const Vector3d &axes)
	{
		glRotated(angle, axes.X, axes.Y, axes.Z);
	}

	void Renderer::setVertexBuffer(IBufferBase *buffer)
	{
		if(buffer != NULL)
		{
			glBindBufferARB(buffer->getType(), buffer->getBuffer());

			for(int i=0; i<buffer->getNbDeclarations(); i++)
			{
				switch(buffer->getDeclaration(i).usage)
				{
					case GL_VERTEX_ARRAY: glVertexPointer(buffer->getDeclaration(i).size_b, GL_FLOAT, 0, BUFFER_OFFSET(buffer->getDeclaration(i).first)); break;
					case GL_NORMAL_ARRAY: glNormalPointer(GL_FLOAT, 0, BUFFER_OFFSET(buffer->getDeclaration(i).first)); break;
					case GL_COLOR_ARRAY: glColorPointer(buffer->getDeclaration(i).size_b, GL_FLOAT, 0, BUFFER_OFFSET(buffer->getDeclaration(i).first)); break;
					case GL_TEXTURE_COORD_ARRAY: glTexCoordPointer(buffer->getDeclaration(i).size_b, GL_FLOAT, 0, BUFFER_OFFSET(buffer->getDeclaration(i).first)); break;
					case GL_INDEX_ARRAY: glIndexPointer(GL_FLOAT, 0, BUFFER_OFFSET(buffer->getDeclaration(i).first)); break;
					case GL_EDGE_FLAG_ARRAY: glEdgeFlagPointer(GL_FLOAT, BUFFER_OFFSET(buffer->getDeclaration(i).first)); break;
					default: break;
				}
				glEnableClientState(buffer->getDeclaration(i).usage);
			}
		}
		else
		{
			glDisableClientState(GL_VERTEX_ARRAY);
			glDisableClientState(GL_NORMAL_ARRAY);
			glDisableClientState(GL_COLOR_ARRAY);
			glDisableClientState(GL_TEXTURE_COORD_ARRAY);
			glDisableClientState(GL_INDEX_ARRAY);
			glDisableClientState(GL_EDGE_FLAG_ARRAY);

			glBindBufferARB(GL_ARRAY_BUFFER, 0);
		}
	}

	void Renderer::setIndexBuffer(IBufferBase *buffer)
	{
		if(buffer != NULL)
		{
			_indexBuffer = buffer;
			glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER, buffer->getBuffer());
		}
		else
			glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER, 0);
	}

	void Renderer::drawPrimitives(GLenum mode, GLsizei count, GLint first)
	{
		glDrawArrays(mode, first, count);
	}

	void Renderer::drawIndexedPrimitives(GLenum mode, GLsizei count)
	{
		glDrawElements(mode, count, GL_UNSIGNED_INT, BUFFER_OFFSET(_indexBuffer->getDeclaration(0).first));
	}

	void Renderer::setTexture(ITexture *texture)
	{
		if(texture != NULL)
		{
			glEnable(GL_TEXTURE_2D);
			glBindTexture(GL_TEXTURE_2D, texture->getGLID());
		}
		else
		{
			glEnable(GL_TEXTURE_2D);
			glBindTexture(GL_TEXTURE_2D, 0);
		}
	}

	void Renderer::setMultiTexture(ITexture *texture, int unit)
	{
		if(texture != NULL)
		{
			switch(unit)
			{
				case 0:
					glActiveTextureARB(GL_TEXTURE0_ARB);
					glEnable(GL_TEXTURE_2D);
					glBindTexture(GL_TEXTURE_2D, texture->getGLID());
					break;
				case 1:
					glActiveTextureARB(GL_TEXTURE1_ARB);
					glEnable(GL_TEXTURE_2D);
					glBindTexture(GL_TEXTURE_2D, texture->getGLID());
					break;
				case 2:
					glActiveTextureARB(GL_TEXTURE2_ARB);
					glEnable(GL_TEXTURE_2D);
					glBindTexture(GL_TEXTURE_2D, texture->getGLID());
					break;
				case 3:
					glActiveTextureARB(GL_TEXTURE3_ARB);
					glEnable(GL_TEXTURE_2D);
					glBindTexture(GL_TEXTURE_2D, texture->getGLID());
					break;
				default:
					glActiveTextureARB(GL_TEXTURE0_ARB);
					glEnable(GL_TEXTURE_2D);
					glBindTexture(GL_TEXTURE_2D, texture->getGLID());
					break;
			}
		}
		else
		{
			switch(unit)
			{
				case 0:
					glActiveTextureARB(GL_TEXTURE0_ARB);
					glEnable(GL_TEXTURE_2D);
					glBindTexture(GL_TEXTURE_2D, 0);
					break;
				case 1:
					glActiveTextureARB(GL_TEXTURE1_ARB);
					glEnable(GL_TEXTURE_2D);
					glBindTexture(GL_TEXTURE_2D, 0);
					break;
				case 2:
					glActiveTextureARB(GL_TEXTURE2_ARB);
					glEnable(GL_TEXTURE_2D);
					glBindTexture(GL_TEXTURE_2D, 0);
					break;
				case 3:
					glActiveTextureARB(GL_TEXTURE3_ARB);
					glEnable(GL_TEXTURE_2D);
					glBindTexture(GL_TEXTURE_2D, 0);
					break;
				default:
					glActiveTextureARB(GL_TEXTURE0_ARB);
					glEnable(GL_TEXTURE_2D);
					glBindTexture(GL_TEXTURE_2D, 0);
					break;
			}
		}
	}
}
